<%@ WebHandler Language="C#" Debug="true" Class="CommunityServer.Blogs.Components.MetaWeblog2" %>
using System;
using System.Collections;
using System.Configuration;
using System.IO;
using System.Text.RegularExpressions;
using System.Web;
using CommunityServer;
using CommunityServer.Blogs.Components;
using CommunityServer.Components;
using CookComputing.XmlRpc;

namespace CommunityServer.Blogs.Components
{
	/// <summary>
	/// MetaWeblog2 extends the core CS 2.1 MetaWeblogAPI web service to include support for the newMediaObject method.
	/// </summary>
	public class MetaWeblog2 : MetaWeblog
	{

		[XmlRpcMissingMapping(MappingAction.Ignore)]
		public struct MediaObject
		{
			public string name;
			public string type;
			public byte[] bits;
		}

		[Serializable]
		public struct MediaObjectInfo
		{
			public string url;
		}

		private string blogFileStorageLocation = "~/blogs";

		// blogFileReadPath is used to override the Storage Location for reading files (usually in a web farm scenario)
		// It's only used if it's not empty.
		private string blogFileReadPath = string.Empty;
		
		private string allowedExtensions = "jpg,jpeg,gif,png,bmp";
		private int maxFileSize = 1024;
		private int maxTotalSize = 8096;
		private readonly string basePattern = "^.+\\.(?:{0})$";
		private Regex _validationRegex = null;

		[XmlRpcMethod("metaWeblog.newMediaObject",
			 Description = "Makes a new post to a designated blog using the MetaWeblog API. Returns file url as a string.")]
		public MetaWeblog2.MediaObjectInfo newMediaObject(string blogid, string username, string password, MetaWeblog2.MediaObject mediaObject)
		{
			if (ValidateUser(username, password))
			{
				Weblog weblog = Weblogs.GetWeblog(blogid, true, true);
				Permissions.AccessCheck(weblog, Permission.Post, CSContext.Current.User);

				// Get custom configuration settings if they were provided
				LoadFileStorageConfiguration();

				try
				{
					if (mediaObject.bits != null && !Globals.IsNullorEmpty(mediaObject.name))
					{
						AddFile(weblog.ApplicationKey, new MemoryStream(mediaObject.bits), mediaObject.bits.Length, mediaObject.name);

						MediaObjectInfo info = new MediaObjectInfo();
						info.url = Globals.FullPath(FileStorageUrl(weblog.ApplicationKey, mediaObject.name));
						return info;
					}
				}
				catch (System.Exception ex)
				{
					EventLogs.Warn("An error occurred while uploading a file via MetaWeblogAPI NewMediaObject.", "Blog File Storage", 320, ex, CSContext.Current.SettingsID);
				}

				throw new XmlRpcFaultException(0, "Invalid file");
			}

			throw new XmlRpcFaultException(0, "User does not exist");

		}

		protected bool ValidateUser(string username, string password)
		{
			try
			{
				User u = new User();
				u.Username = username;
				u.Password = password;
				CommunityServer.Components.LoginUserStatus status = Users.ValidUser(u);
				if (status == CommunityServer.Components.LoginUserStatus.Success)
				{
					//System.Web.Security.FormsAuthentication.SetAuthCookie(u.Username,false);
					CSContext.Current.User = Users.GetUser(-1, u.Username, false, false);
					return true;
				}
			}
			catch { }

			return false;
		}

		protected void LoadFileStorageConfiguration()
		{
			if (!Globals.IsNullorEmpty(ConfigurationSettings.AppSettings["BlogFiles_ReadPath"]))
				blogFileReadPath = ConfigurationSettings.AppSettings["BlogFiles_ReadPath"].Trim();

			if (!Globals.IsNullorEmpty(ConfigurationSettings.AppSettings["BlogFiles_StoragePath"]))
				blogFileStorageLocation = ConfigurationSettings.AppSettings["BlogFiles_StoragePath"].Trim();

			if (!Globals.IsNullorEmpty(ConfigurationSettings.AppSettings["BlogFiles_ExtensionList"]))
				allowedExtensions = ConfigurationSettings.AppSettings["BlogFiles_ExtensionList"].Trim().ToLower();
			
			string temp = ConfigurationSettings.AppSettings["BlogFiles_MaxFileSize"];
			if (!Globals.IsNullorEmpty(temp))
			{
				try
				{
					maxFileSize = Int32.Parse(temp);
				}
				catch { }
			}

			temp = ConfigurationSettings.AppSettings["BlogFiles_MaxTotalSize"];
			if (!Globals.IsNullorEmpty(temp))
			{
				try
				{
					maxTotalSize = Int32.Parse(temp);
				}
				catch { }
			}

		}

		protected void AddFile(string appKey, Stream contentStream, int contentLength, string fileName)
		{
			// Check if file is under the maximum size setting
			if ((contentLength / 1024) > maxFileSize && maxFileSize > 0)
				throw new CSException(CSExceptionType.PostAttachmentTooLarge, "The size of this blog file exceeds the maximum limit for this site.");

			// Check if blog has exceeded the quota (if one is configured)
			if (maxTotalSize > 0)
			{
				long totalSize = GetTotalFileSize(appKey) + Convert.ToInt64(contentLength / 1024);
				if (totalSize > maxTotalSize)
					throw new CSException(CSExceptionType.PostAttachmentTooLarge, "The size of this blog file would cause the blog to exceed the maximum total file size.");
			}

			// Strip out path if one was included
			int i = fileName.LastIndexOf(Path.DirectorySeparatorChar);
			if (i != -1)
				fileName = fileName.Substring(i + 1);

			// Ensure file extension is allowed
			if (!CSContext.Current.User.IsBlogAdministrator && allowedExtensions != "all")
			{
				if (!ValidationRegex.IsMatch(fileName))
					throw new CSException(CSExceptionType.PostAttachmentsNotAllowed, string.Format(ResourceManager.GetString("UploadAttachment_InvalidFileExtension"), allowedExtensions));
			}

			// Write file to disk
			SaveToDisk(contentStream, Path.Combine(GetBlogFileWriteFolder(appKey), fileName));

			EventLogs.Info(string.Format("The file {0} was uploaded to the blog {1}", fileName, appKey), "Blog File Storage", 320);
		}

		protected long GetTotalFileSize(string appKey)
		{
			long size = DirSize(new DirectoryInfo(GetBlogFileWriteFolder(appKey)));
			size = size / 1024;
			return size;
		}

		protected long DirSize(DirectoryInfo parentDir)
		{
			long size = 0;
			if (parentDir != null && parentDir.Exists)
			{
				foreach (FileInfo fi in parentDir.GetFiles())
				{
					size += fi.Length;
				}
				DirectoryInfo[] subDir = parentDir.GetDirectories();
				foreach (DirectoryInfo di in subDir)
				{
					size += DirSize(di);
				}
			}
			return size;
		}

		protected void SaveToDisk(Stream content, string name)
		{
			FileInfo fi = new FileInfo(name);
			if (!fi.Directory.Exists)
				fi.Directory.Create();

			using (FileStream fs = new FileStream(name, FileMode.Create, FileAccess.Write, FileShare.None))
			{
				byte[] buffer = new byte[256 * 1024];
				int bytes;

				while ((bytes = content.Read(buffer, 0, 256 * 1024)) > 0)
					fs.Write(buffer, 0, bytes);
				fs.Close();
			}
		}

		/// <summary>
		/// Regex which can be used to validate if the current file is of the correct type to save.
		/// </summary>
		protected Regex ValidationRegex
		{
			get
			{
				if (_validationRegex == null)
				{
					string validationPattern = (allowedExtensions.Length > 0) ? string.Format(basePattern, allowedExtensions.Replace(',', '|')) : ".+";
					_validationRegex = new Regex(validationPattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
				}
				return _validationRegex;
			}
		}

		protected string FileStorageUrl(string applicationkey, string fileName)
		{
			string rootPath = null;
			if (!Globals.IsNullorEmpty(blogFileReadPath))
				rootPath = blogFileReadPath.Replace("~", Globals.ApplicationPath);
			else
				rootPath = blogFileStorageLocation.Replace("~", Globals.ApplicationPath);

			string url = string.Format("{0}/{1}/{2}", rootPath, applicationkey, fileName);

			// Replace backslashes with forward ones and remove any extra slashes
			url = url.Replace("\\", "/").Replace("//", "/");

			// Ensure url starts with a slash
			if (!url.StartsWith("/"))
				url = url.Insert(0, "/");

			return url;
		}

		protected string GetBlogFileWriteFolder(string appKey)
		{
			string calculatedFileSystemStorageLocation = null;

			// Make sure it isn't a drive reference like "c:\blah" or a UNC name like "\\machine\share"
			if ((blogFileStorageLocation.IndexOf(":\\") != -1) || (blogFileStorageLocation.IndexOf("\\\\") != -1))
				calculatedFileSystemStorageLocation = blogFileStorageLocation;
			else //if(CSContext.Current.IsWebRequest) the CSContext MapPath can already handle non web requests
				calculatedFileSystemStorageLocation = CSContext.Current.MapPath(blogFileStorageLocation);

			// Add the end directory separator if missing
			if (!calculatedFileSystemStorageLocation.EndsWith(Path.DirectorySeparatorChar.ToString()))
				calculatedFileSystemStorageLocation += Path.DirectorySeparatorChar;

			return Path.Combine(calculatedFileSystemStorageLocation, appKey);
		}

	}
}